#include <GameEngine/GameEngine/square_map_renderer.h>

#include <GameEngine/Maps/map_cell.h>
#include <GameEngine/Maps/terrain_descriptor.h>
#include <GameEngine/Geometry/Location/vector2d.h>
#include <GameEngine/Geometry/Surfaces/parallelogram3d.h>
#include <GameEngine/Graphics/Texture/texture_coordinates.h>

#include <QtGui/QImage>

#include <iostream>

#ifndef DOXYGEN_IGNORE


#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif


//! Default constructor
SquareMapRenderer::SquareMapRenderer() : map_(nil), SceneElement(GeometryOpaqueElement) {}

//! Build the rendering
/*!
 @param map The square map to render
 @param path If not nil, also renders the given path
 */
void SquareMapRenderer::build(const SquareMap& map, std::list<SquareMapSearch::Node>* path) {
	// Get map
	map_ = &map;

	// Check path
	if ( path != nil ) {
		if ( path->empty() ) path = nil;
	}

	// Read wall texture
	QImage img; img.load("..\\GameEngineMisc\\data\\images\\Wall.png");
	wall_texture_.bind_texture(true);
	wall_texture_.set_parameters(Texture::UseMipmapMode);
	wall_texture_.set_parameters(Texture::ModulateMode);
	wall_texture_.set_parameters(Texture::RepeatMode);
	wall_texture_.set_parameters(Texture::LinearMagFilter);
	wall_texture_.set_parameters(Texture::LinearMipmapLinearMinFilter);
	wall_texture_.set_texture(img);
	wall_texture_.unbind_texture();

	// Read ground texture
	img.load("..\\GameEngineMisc\\data\\images\\Grass.jpg");
	ground_texture_.bind_texture();
	ground_texture_.set_parameters(Texture::UseMipmapMode);
	ground_texture_.set_parameters(Texture::ModulateMode);
	ground_texture_.set_parameters(Texture::RepeatMode);
	ground_texture_.set_parameters(Texture::LinearMagFilter);
	ground_texture_.set_parameters(Texture::LinearMipmapLinearMinFilter);
	ground_texture_.set_texture(img);
	ground_texture_.unbind_texture(true);

	// Read sea texture
	img.load("..\\GameEngineMisc\\data\\images\\Sea.jpg");
	sea_texture_.bind_texture();
	sea_texture_.set_parameters(Texture::UseMipmapMode);
	sea_texture_.set_parameters(Texture::ModulateMode);
	sea_texture_.set_parameters(Texture::RepeatMode);
	sea_texture_.set_parameters(Texture::LinearMagFilter);
	sea_texture_.set_parameters(Texture::LinearMipmapLinearMinFilter);
	sea_texture_.set_texture(img);
	sea_texture_.unbind_texture(true);

	// Read sand texture
	img.load("..\\GameEngineMisc\\data\\images\\Sand.jpg");
	sand_texture_.bind_texture();
	sand_texture_.set_parameters(Texture::UseMipmapMode);
	sand_texture_.set_parameters(Texture::ModulateMode);
	sand_texture_.set_parameters(Texture::RepeatMode);
	sand_texture_.set_parameters(Texture::LinearMagFilter);
	sand_texture_.set_parameters(Texture::LinearMipmapLinearMinFilter);
	sand_texture_.set_texture(img);
	sand_texture_.unbind_texture(true);

	// Read swamp texture
	img.load("..\\GameEngineMisc\\data\\images\\Swamp.jpg");
	swamp_texture_.bind_texture();
	swamp_texture_.set_parameters(Texture::UseMipmapMode);
	swamp_texture_.set_parameters(Texture::ModulateMode);
	swamp_texture_.set_parameters(Texture::RepeatMode);
	swamp_texture_.set_parameters(Texture::LinearMagFilter);
	swamp_texture_.set_parameters(Texture::LinearMipmapLinearMinFilter);
	swamp_texture_.set_texture(img);
	swamp_texture_.unbind_texture(true);

	// Prepare texture coordinates
	float mult = 1.0f;
	TextureCoordinate2d c0(0.0f,0.0f);
	TextureCoordinate2d c1(mult,0.0f);
	TextureCoordinate2d c2(mult,mult);
	TextureCoordinate2d c3(0.0f,mult);
	mult = 2.0f;
	TextureCoordinate2d c0p(0.0f,0.0f);
	TextureCoordinate2d c1p(mult,0.0f);
	TextureCoordinate2d c2p(mult,mult);
	TextureCoordinate2d c3p(0.0f,mult);

	// Draw the map in the display list
	list_.begin_display_list();

	// Get the cell axes
	Vector2d<float> axis1 = map.map_box().v1() / (float)map.width();
	Vector2d<float> axis2 = map.map_box().v2() / (float)map.height();

	// Enable 2D texture
	glEnable(GL_TEXTURE_2D);

	// Draw the cells
	glLineWidth(1);
	for ( Map::MapIndex k = 0; k < map.width(); ++k ) {
		for ( Map::MapIndex l = 0; l < map.height(); ++l ) {
			// Get the current cell of the square map
			SquareMap::Cell* cell = map.get_cell(k,l);
			if ( cell == nil ) continue;

			// Get the cell origin
			Point2d<float> v1 = map.map_box().origin();
			v1.x() += ( (float)k * map.map_box().v1().x() / (float)map.width() ) +
					  ( (float)l * map.map_box().v2().x() / (float)map.height() );
			v1.y() += ( (float)k * map.map_box().v1().y() / (float)map.width() ) +
					  ( (float)l * map.map_box().v2().y() / (float)map.height() );
			Point2d<float> v2 = Point2d<float>(v1.x()+axis1.x(),v1.y()+axis1.y());
			Point2d<float> v3 = Point2d<float>(v1.x()+axis1.x()+axis2.x(),v1.y()+axis1.y()+axis2.y());
			Point2d<float> v4 = Point2d<float>(v1.x()+axis2.x(),v1.y()+axis2.y());

			// Get the cell color
			glColor3f(1,1,1);
			if ( cell->terrain() == TerrainDescriptor::Grass ) ground_texture_.bind_texture();
			else if ( cell->terrain() == TerrainDescriptor::Sand ) sand_texture_.bind_texture();
			else if ( cell->terrain() == TerrainDescriptor::Swamp ) swamp_texture_.bind_texture();
			else if ( cell->terrain() == TerrainDescriptor::Sea ) sea_texture_.bind_texture();
			else wall_texture_.bind_texture();
			if ( path != nil ) {
				// Get the first index
				Map::MapIndex x1, y1, x2, y2;
				map.get_axes_indexes(x1,y1,*path->begin());
				map.get_axes_indexes(x2,y2,*path->rbegin());
				if ( ( k == x1 ) && ( l == y1 ) ) glColor3f(0,0,1);
				else if ( ( k == x2 ) && ( l == y2 ) ) glColor3f(0,1,0);
			}

			// Draw the cell surface
			glNormal3f(0,0,1);
			glBegin(GL_QUADS);
				c0p.apply_coordinates(); glVertex2f(v1.x(),v1.y());
				c1p.apply_coordinates(); glVertex2f(v2.x(),v2.y());
				c2p.apply_coordinates(); glVertex2f(v3.x(),v3.y());
				c3p.apply_coordinates(); glVertex2f(v4.x(),v4.y());
			glEnd();

			// If cell is a wall, draw it as a volume
			float height = 0.05f;
			if ( cell->terrain() == TerrainDescriptor::Wall ) {
				glBegin(GL_QUADS);
					c0.apply_coordinates(); glVertex3f(v1.x(),v1.y(),height);
					c1.apply_coordinates(); glVertex3f(v2.x(),v2.y(),height);
					c2.apply_coordinates(); glVertex3f(v3.x(),v3.y(),height);
					c3.apply_coordinates(); glVertex3f(v4.x(),v4.y(),height);
				glEnd();
				glNormal3f(1,0,0);
				glBegin(GL_QUADS);
					c0.apply_coordinates(); glVertex3f(v1.x(),v1.y(),height);
					c1.apply_coordinates(); glVertex3f(v2.x(),v2.y(),height);
					c2.apply_coordinates(); glVertex3f(v2.x(),v2.y(),0);
					c3.apply_coordinates(); glVertex3f(v1.x(),v1.y(),0);
				glEnd();
				glNormal3f(0,1,0);
				glBegin(GL_QUADS);
					c0.apply_coordinates(); glVertex3f(v2.x(),v2.y(),height);
					c1.apply_coordinates(); glVertex3f(v3.x(),v3.y(),height);
					c2.apply_coordinates(); glVertex3f(v3.x(),v3.y(),0);
					c3.apply_coordinates(); glVertex3f(v2.x(),v2.y(),0);
				glEnd();
				glNormal3f(-1,0,0);
				glBegin(GL_QUADS);
					c0.apply_coordinates(); glVertex3f(v3.x(),v3.y(),height);
					c1.apply_coordinates(); glVertex3f(v4.x(),v4.y(),height);
					c2.apply_coordinates(); glVertex3f(v4.x(),v4.y(),0);
					c3.apply_coordinates(); glVertex3f(v3.x(),v3.y(),0);
				glEnd();
				glNormal3f(0,-1,0);
				glBegin(GL_QUADS);
					c0.apply_coordinates(); glVertex3f(v4.x(),v4.y(),height);
					c1.apply_coordinates(); glVertex3f(v1.x(),v1.y(),height);
					c2.apply_coordinates(); glVertex3f(v1.x(),v1.y(),0);
					c3.apply_coordinates(); glVertex3f(v4.x(),v4.y(),0);
				glEnd();
			}
			if ( cell->terrain() == TerrainDescriptor::Grass ) {
				ground_texture_.unbind_texture();
			}
			else {
				wall_texture_.unbind_texture();
			}
		}
	}

	// Draw the path
	if ( path != nil ) {
		// Useful variables
		Map::MapIndex x, y;
		Point2d<float> prev_pos;
		int index = 0;
		float nb_nodes = (float)path->size();
		glLineWidth(5);

		// Iterate over the path
		for ( std::list<SquareMapSearch::Node>::iterator it = path->begin(); it != path->end(); ++it ) {
			// Get current node
			map.get_axes_indexes(x,y,*it);

			// Get node position
			Point2d<float> pos = map.map_box().origin();
			pos.x() += ( (float)x * map.map_box().v1().x() / (float)map.width() ) +
					   ( (float)y * map.map_box().v2().x() / (float)map.height() ) +
					   axis1.x() / 2.0 + axis2.x() / 2.0;
			pos.y() += ( (float)x * map.map_box().v1().y() / (float)map.width() ) +
					   ( (float)y * map.map_box().v2().y() / (float)map.height() ) +
					   axis1.y() / 2.0 + axis2.y() / 2.0;

			if ( index ) {
				float height = 0.001f;
				float col = (float)index/(float)(nb_nodes-1);
				glBegin(GL_LINES);
					glVertex3f(prev_pos.x(), prev_pos.y(), height);
					glColor3f( 1.0f-col, 1.0f-col, 1 );
					glVertex3f(pos.x(), pos.y(), height);
				glEnd();
			}
			else {
				glColor3f( 1, 1, 1 );
			}
			++index;
			prev_pos = pos;
		}
	}

	// Finish the drawing
	list_.end_display_list();
}

//! Destructor
SquareMapRenderer::~SquareMapRenderer() {}

//! Draw the map
/*!
 @param ray The picking ray under the mouse
 @return Always return true
 */
bool SquareMapRenderer::draw(const Line3d<float>& ray) {
	// Draw the whole map
	list_.draw();

	// Test cell under the picking location
	float line_coordinate = ray.p1().z() / ( ray.p1().z() - ray.p2().z() );
	Point3d<float> pt = ray.point(line_coordinate);
	std::vector< Point2d<float> > cell = map_->get_cell_geometry( Point2d<float>(pt.x(), pt.y()) );

	// Draw the cell wireframe if there is such a cell
	if ( cell.empty() == false ) {
		float height = 0.0001f;
		glColor3f(0,0,0);
		glBegin(GL_LINE_STRIP);
			for ( unsigned int k = 0; k < cell.size(); ++k ) {
				glVertex3f(cell[k].x(),cell[k].y(),height);
			}
			glVertex3f(cell[0].x(),cell[0].y(),height);
		glEnd();
	}
	return true;
}



#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif

#endif
